#include <gtest/gtest.h>
#include "Program.h"
#include <iostream>
#include "Routine.h"
#include "Instruction.h"
#include "Expression.h"
#include "Data.h"
#include "CREATE_VARIABLE.h"
#include "ASSIGN_VARIABLE.h"
#include "Memory.h"
#include "Variable.h"
#include "DataHolderGarbageCollector.h"

TEST(ProgramTest, CreateVariableAndInitializeWithVariable) {
	
	Program p("TestProgram");
	
	Routine* main = new Routine();
	
	CREATE_VARIABLE_Instruction* createVar1 = 
		new CREATE_VARIABLE_Instruction("a", Expression::createSingleInt(3, p.getMemory()));
	
	Expression* assignThis = Expression::createExpression(DataHolder::INT, p.getMemory());
	assignThis->addSubExpression(Data::createVariableName("a"));
	
	CREATE_VARIABLE_Instruction* createVar2 = new CREATE_VARIABLE_Instruction("b", assignThis);
	
	main->addInstruction(createVar1);
	main->addInstruction(createVar2);
	
	p.addRoutine(main);
	p.run();
	DHGC->collectGarbage();
	
	Memory* memory = p.getMemory();
	Variable* var1 = memory->getVariable("a");
	Variable* var2 = memory->getVariable("b");
	EXPECT_EQ(var1->getIntValue(), 3);
	EXPECT_EQ(var2->getIntValue(), 3);
}

TEST(ProgramTest, VariableReferences) {
	
	Program p("TestProgram");
	
	Routine* main = new Routine();
	
	main->addInstruction(
		new CREATE_VARIABLE_Instruction("a", Expression::createSingleInt(3, p.getMemory())));
	main->addInstruction(
		new CREATE_VARIABLE_Instruction("b", Expression::createSingleInt(4, p.getMemory())));
	main->addInstruction(
		new CREATE_VARIABLE_Instruction("c", Expression::createSingleInt(5, p.getMemory())));
	
	Expression* e = Expression::createExpression(DataHolder::INT, p.getMemory(), Expression::MULTIPLY);
	e->addSubExpression(Data::createVariableName("a"));
	e->addSubExpression(Data::createVariableName("b"));
	e->addSubExpression(Data::createVariableName("c"));
	
	main->addInstruction(new CREATE_VARIABLE_Instruction("sum", e));
	
	p.addRoutine(main);
	p.run();
	DHGC->collectGarbage();
	
	Memory* memory = p.getMemory();
	Variable* sum = memory->getVariable("sum");
	EXPECT_EQ(sum->getIntValue(), 60);
}

TEST(ProgramTest, VariableReferenceChain) {

	Program program("TestProgram");
	
	Routine* main = new Routine();
	
	main->addInstruction(new CREATE_VARIABLE_Instruction(
		"a", Expression::createSingleString("Hello!", program.getMemory())));
	main->addInstruction(new CREATE_VARIABLE_Instruction(
		"b", Expression::createSingleVariableName("a", program.getMemory())));
	main->addInstruction(new CREATE_VARIABLE_Instruction(
        "c", Expression::createSingleVariableName("b", program.getMemory())));
	
	Expression* e = Expression::createExpression(DataHolder::STRING, 
												 program.getMemory(), 
												 Expression::SINGLE_VALUE);
	e->addSubExpression(Data::createVariableName("c"));
	
	main->addInstruction(new CREATE_VARIABLE_Instruction("d", e));
	
	program.addRoutine(main);
	program.run();
	DHGC->collectGarbage();
	
	Memory* memory = program.getMemory();
	Variable* d = memory->getVariable("d");
	EXPECT_EQ(d->getStringValue(), "Hello!");
}


TEST(ProgramTest, CollectingGarbageAfterProgramHasRun) {
	
	DHGC->deleteAll();
	
	{
		Program program("TestProgram");
		
		Routine* main = new Routine();
		
		Expression* expression = Expression::createSingleInt(1000, program.getMemory());
		
		main->addInstruction(new CREATE_VARIABLE_Instruction("a", expression));
		
		program.addRoutine(main);
		program.run();
		
		EXPECT_EQ(Expression::getNrOfInstances(), 1);
		EXPECT_EQ(Variable::getNrOfInstances(), 1);
		
		for (int i = 0; i < 100; i++) {
			program.run();
		}
		
		DHGC->collectGarbage(); // this shouldn't delete any expressions or variables in the program
		
		EXPECT_EQ(Expression::getNrOfInstances(), 1);
		EXPECT_EQ(Variable::getNrOfInstances(), 1);
	}
	
	DHGC->collectGarbage();
	
	EXPECT_EQ(Expression::getNrOfInstances(), 0);
	EXPECT_EQ(Variable::getNrOfInstances(), 0);
	EXPECT_EQ(Data::getNrOfInstances(), 0);
}


TEST(ProgramTest, SimpleProgramThatUsesThe_ASSIGN_VARIABLE_Instruction) {
	
	/*
	
	 int a = 3
	 int b = 5
	 b = a + b
	 
	*/
	
	Program program("TestProgram");
	Routine* routine = new Routine();
	
	routine->addInstruction(new CREATE_VARIABLE_Instruction("a", Expression::createSingleInt(3, program.getMemory())));
	routine->addInstruction(new CREATE_VARIABLE_Instruction("b", Expression::createSingleInt(5, program.getMemory())));
	
	Expression* assignExpression = Expression::createExpression(DataHolder::INT, 
																program.getMemory(), 
																Expression::ADD);
	
	assignExpression->addSubExpression(Data::createVariableName("a"));
	assignExpression->addSubExpression(Data::createVariableName("b"));
	
	ASSIGN_VARIABLE_Instruction* instruction = new ASSIGN_VARIABLE_Instruction("b", assignExpression);
	routine->addInstruction(instruction);
	
	program.addRoutine(routine);
	program.run();
	
	EXPECT_EQ(program.getMemory()->getVariable("a")->getIntValue(), 3);
	EXPECT_EQ(program.getMemory()->getVariable("b")->getIntValue(), 8);
}



